package vg.services.graph_view_manager.realization.graph_view.smart_graph_view.realization;

import java.awt.event.*;
import java.util.*;

import javax.swing.*;

import com.mxgraph.analysis.mxGraphAnalysis;
import com.mxgraph.analysis.mxICostFunction;
import com.mxgraph.model.mxCell;
import com.mxgraph.swing.handler.mxKeyboardHandler;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.mxGraphOutline;
import com.mxgraph.util.*;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxStylesheet;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.mutable.MutableBoolean;

import vg.services.data_base_manager.data.graph.*;
import vg.services.graph_view_manager.interfaces.graph_view.AGraphView;
import vg.services.graph_view_manager.interfaces.graph_view.IGraphObserver;
import vg.services.graph_view_manager.interfaces.graph_view.IGraphRenderer;
import vg.services.graph_view_manager.realization.graph_view.smart_graph_view.SmartGraphView;
import vg.services.main_manager.MainManager;
import vg.services.plugin_manager.event_and_request.event.graph_editor.AddAttributeEvent;
import vg.services.plugin_manager.event_and_request.event.graph_editor.AddEdgeEvent;
import vg.services.plugin_manager.event_and_request.event.graph_editor.AddVertexEvent;
import vg.services.plugin_manager.event_and_request.request.UIRequestAddTab;
import vg.shared.graph_editor.SharedGraphEditor;

/**
 * Implementation of ISGVGraphView4Patching.
 *
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */
public class SGVGraphRenderer implements IGraphRenderer {
    // Defines
    private static final String DEF_NAME_ATTRIBUTE = "name";
    private static final String DEF_SHAPE_ATTRIBUTE = "shape";
    private static final String DEF_COLOR_ATTRIBUTE = "color";

    // Style data
    private mxStylesheet styleSheet;

    private int	spacingLeft;
    private int	spacingTop;
    private int	spacingRight;
    private int	spacingBottom;

    private String fontColor;
    private String fillColor;
    private String vertexShape;
    private String graphElementsFont;
    private String vertexTextAlign;

    // Main components
    private AGraphView parent;
    private mxGraph jgraph;
    private mxGraphComponent jgraph_view;
    private mxGraphOutline jgraph_minimap;

    private mxUndoManager jgraph_undo_manager;

    // Main data
    private Graph graph;
    private int graph_model_id;

    private Map<mxCell, Vertex> cell_vertex; // Cell(4 Vertex) -> Vertex
    private Map<mxCell, Edge> cell_edge;     // Cell(4 Edge) -> Edge
    private Map<Vertex, mxCell> vertex_cell; // Vertex -> Cell(4 Vertex)
    private Map<Edge, mxCell> edge_cell;     // Edge -> Cell(4 Edge)
    private Map<Vertex, Map<Attribute, MutableBoolean>> vertex_attributes;
    private Map<Edge, Map<Attribute, MutableBoolean>> edge_attributes;

    private List<IGraphObserver> observers;

    private JPopupMenu popup;

    private boolean panning = false;

    public SGVGraphRenderer(AGraphView parent) {
        this.parent = parent;

        reset();
    }

//==============================================================================
//-----------------Graph building methods---------------------------------------
    @Override
    public void merge(Graph inGraph, IGraphRenderer inGraphRenderer) {
        reset();

        SGVGraphRenderer sgvInGraphRenderer = null;
        if (inGraphRenderer instanceof SGVGraphRenderer)
            sgvInGraphRenderer = (SGVGraphRenderer)inGraphRenderer;

        // copy graph settings
        spacingLeft = sgvInGraphRenderer.spacingLeft;
        spacingTop = sgvInGraphRenderer.spacingTop;
        spacingRight = sgvInGraphRenderer.spacingRight;
        spacingBottom = sgvInGraphRenderer.spacingBottom;

        fontColor = sgvInGraphRenderer.fontColor;
        fillColor = sgvInGraphRenderer.fillColor;
        vertexShape = sgvInGraphRenderer.vertexShape;
        graphElementsFont = sgvInGraphRenderer.graphElementsFont;
        vertexTextAlign = sgvInGraphRenderer.vertexTextAlign;

        // copy graph
        setDirected(inGraph.isDirected());

        Object parent = jgraph.getDefaultParent();
        jgraph.getModel().beginUpdate();
        for (Vertex vertex : inGraph.getVertices()) {
            mxCell v_src = sgvInGraphRenderer.vertex_cell.get(vertex);
            mxCell v_dst = null;
            if (v_src != null) {
                store_attributes4vertex(vertex, sgvInGraphRenderer.vertex_attributes.get(vertex));
                v_dst = (mxCell)jgraph.insertVertex(parent, v_src.getId(), v_src.getValue(),
                        v_src.getGeometry().getX(), v_src.getGeometry().getY(),
                        v_src.getGeometry().getWidth(), v_src.getGeometry().getHeight(), v_src.getStyle());
            } else {
                store_attributes4vertex(vertex);
                String title = generate_text4vertex(vertex);
                v_dst = (mxCell)jgraph.insertVertex(parent, Integer.toString(vertex.getStorableId()), title, 0, 0, 100, 30);
            }
            vertex_cell.put(vertex, v_dst);
            cell_vertex.put(v_dst, vertex);
            graph.addVertex(vertex);
            auto_generate_style4vertex(vertex);
        }

        for (Edge edge : inGraph.getEdges()) {
            mxCell e_src = sgvInGraphRenderer.edge_cell.get(edge);
            mxCell e_dst = null;

            Object src = vertex_cell.get(edge.getSource());
            Object trg = vertex_cell.get(edge.getTarget());

            if (src == null || trg == null) continue;

            if (e_src != null) {
                store_attributes4edge(edge, sgvInGraphRenderer.edge_attributes.get(edge));
                e_dst = (mxCell)jgraph.insertEdge(parent, e_src.getId(), e_src.getValue(), src, trg, e_src.getStyle());
                e_dst.setGeometry(e_src.getGeometry());
            } else {
                store_attributes4edge(edge);
                e_dst = (mxCell)jgraph.insertEdge(parent, Integer.toString(edge.getStorableId()), null, src, trg);
            }
            edge_cell.put(edge, (mxCell)e_dst);
            cell_edge.put((mxCell)e_dst, edge);
            graph.addEdge(edge);
            auto_generate_style4edge(edge);
        }
        jgraph.getModel().endUpdate();

        // zoom
        double x = 0;
        double y = 0;

        Object[] cells = jgraph.getChildCells(jgraph.getDefaultParent());
        boolean initialize = false;
        for (Object cell : cells) {
            mxCellState state = jgraph.getView().getState(cell);
            if (!initialize) {
                x = state.getLabelBounds().getX();
                y = state.getLabelBounds().getY();
                initialize = true;
            }
            if (state.getLabelBounds().getX() < x)
                x = state.getLabelBounds().getX();

            if (state.getLabelBounds().getY() < y)
                y = state.getLabelBounds().getY();
        }
        if (initialize)
            jgraph.moveCells(jgraph.getChildCells(jgraph.getDefaultParent()), -x, -y);

        double zoomfactor = jgraph.getView().getScale();
        double zoomfactorx = zoomfactor / jgraph.getView().getGraphBounds().getWidth() * sgvInGraphRenderer.jgraph_view.getWidth();
        double zoomfactory = zoomfactor / jgraph.getView().getGraphBounds().getHeight() * sgvInGraphRenderer.jgraph_view.getHeight();

        jgraph.getView().setScale(Math.max(Math.min(zoomfactorx, zoomfactory), zoomfactor));
        refreshView();
    }

    @Override
    public void reset() {
        graph = new Graph(false);

        vertex_cell = new HashMap<Vertex, mxCell>();
        edge_cell = new HashMap<Edge, mxCell>();
        cell_vertex = new HashMap<mxCell, Vertex>();
        cell_edge = new HashMap<mxCell, Edge>();

        vertex_attributes = new HashMap<Vertex, Map<Attribute,MutableBoolean>>();
        edge_attributes = new HashMap<Edge, Map<Attribute,MutableBoolean>>();

        gui_setup();
    }

    @Override
    public void resetGraph() {
        jgraph.cellsRemoved(edge_cell.values().toArray());
        jgraph.cellsRemoved(vertex_cell.values().toArray());

        graph = new Graph(false);

        vertex_cell = new HashMap<Vertex, mxCell>();
        edge_cell = new HashMap<Edge, mxCell>();
        cell_vertex = new HashMap<mxCell, Vertex>();
        cell_edge = new HashMap<mxCell, Edge>();

        vertex_attributes = new HashMap<Vertex, Map<Attribute,MutableBoolean>>();
        edge_attributes = new HashMap<Edge, Map<Attribute,MutableBoolean>>();

        turnObservers_resetGraph();
    }

    @Override
    public void setDirected(final boolean directed) {
        graph.setDirected(directed);
        refreshView();
    }

    @Override
    public void addVertex(Vertex vertex) {
        if (vertex == null) return;

        Object parent = jgraph.getDefaultParent();

        jgraph.getModel().beginUpdate();
        try {
            store_attributes4vertex(vertex);
            String title = generate_text4vertex(vertex);
            Object v = jgraph.insertVertex(parent, Integer.toString(vertex.getStorableId()), title, 0, 0, 100, 30);
            vertex_cell.put(vertex, (mxCell)v);
            cell_vertex.put((mxCell)v, vertex);
            graph.addVertex(vertex);
            auto_generate_style4vertex(vertex);
            mark_vertex_with_inner_graph(vertex);
        } finally {
            jgraph.getModel().endUpdate();
        }

        turnObservers_changeGraphStructure();
    }

    @Override
    public void addVertices(Collection<Vertex> vertices) {
        if (vertices == null || vertices.isEmpty()) return;

        Object parent = jgraph.getDefaultParent();

        jgraph.getModel().beginUpdate();
        try {
            for (Vertex vertex : vertices) {
                store_attributes4vertex(vertex);
                String title = generate_text4vertex(vertex);
                Object v = jgraph.insertVertex(parent, Integer.toString(vertex.getStorableId()), title, 0, 0, 100, 30);
                vertex_cell.put(vertex, (mxCell)v);
                cell_vertex.put((mxCell)v, vertex);
                graph.addVertex(vertex);
                auto_generate_style4vertex(vertex);
                mark_vertex_with_inner_graph(vertex);
            }
        } finally {
            jgraph.getModel().endUpdate();
        }

        turnObservers_changeGraphStructure();
    }

    @Override
    public void addEdge(Edge edge) {
        if (edge == null) return;

        Object parent = jgraph.getDefaultParent();

        jgraph.getModel().beginUpdate();
        try {
            Object src = vertex_cell.get(edge.getSource());
            Object trg = vertex_cell.get(edge.getTarget());
            if (src != null && trg != null) {
                store_attributes4edge(edge);
                Object e = jgraph.insertEdge(parent, Integer.toString(edge.getStorableId()), null, src, trg);
                edge_cell.put(edge, (mxCell)e);
                cell_edge.put((mxCell)e, edge);
                graph.addEdge(edge);
                auto_generate_style4edge(edge);
            }
        } finally {
            jgraph.getModel().endUpdate();
        }

        turnObservers_changeGraphStructure();
    }

    @Override
    public void addEdges(Collection<Edge> edges) {
        if (edges == null || edges.isEmpty()) return;

        Object parent = jgraph.getDefaultParent();

        jgraph.getModel().beginUpdate();
        try {
            for (Edge edge : edges) {
                Object src = vertex_cell.get(edge.getSource());
                Object trg = vertex_cell.get(edge.getTarget());
                if (src != null && trg != null) {
                    store_attributes4edge(edge);
                    Object e = jgraph.insertEdge(parent, Integer.toString(edge.getStorableId()), null, src, trg);
                    edge_cell.put(edge, (mxCell)e);
                    cell_edge.put((mxCell)e, edge);
                    graph.addEdge(edge);
                    auto_generate_style4edge(edge);
                }
            }
        } finally {
            jgraph.getModel().endUpdate();
        }

        turnObservers_changeGraphStructure();
    }

    @Override
    public void addAttribute4Vertex(Attribute attr, Vertex vertex) {
        mxCell cell = vertex_cell.get(vertex);

        if (cell != null && vertex != null && attr != null) {
            vertex.addAttribute(attr);
            store_attribute4vertex(vertex, attr);
            String text = generate_text4vertex(vertex);
            cell.setValue(text);
            // m.b. need call repaint
            jgraph_view.refresh();
        }

        turnObservers_changeGraphStructure();
    }

    @Override
    public void addAttribute4Edge(Attribute attr, Edge edge) {
        mxCell cell = edge_cell.get(edge);

        if (cell != null && edge != null && attr != null) {
            edge.addAttribute(attr);
            store_attribute4edge(edge, attr);
            String text = generate_text4edge(edge);
            cell.setValue(text);
            // m.b. need call repaint
            jgraph_view.refresh();
        }

        turnObservers_changeGraphStructure();
    }

    @Override
    public void setDataBaseGraphId(int graphId) {
        graph.setStorableId(graphId);

        turnObservers_changeGraphStructure();
    }

    @Override
    public void setDataBaseGraphModelId(int graphModelId) {
        this.graph_model_id = graphModelId;

        turnObservers_changeGraphStructure();
    }

//==============================================================================
//-----------------Graph observer methods---------------------------------------
    @Override
    public boolean addObserver(IGraphObserver observer) {
        return observers.add(observer);
    }

    @Override
    public boolean removeObserver(IGraphObserver observer) {
        observers.remove(observer);
        return true;
    }

//==============================================================================
//-----------------Graph view methods-------------------------------------------
    @Override
    public JComponent getView() {
        return jgraph_view;
    }

    @Override
    public JComponent getMiniMap() {
        return jgraph_minimap;
    }

    @Override
    public void refreshView() {
        apply_settings();
        mark_vertices_with_inner_graphs();
    }

//=============================================================================
//-----------------Methods for getting information about graph elements--------
    @Override
    public Map<Edge, Map<Attribute, MutableBoolean>> getEdgeAttributes() {
        return edge_attributes;
    }

    @Override
    public Map<Vertex, Map<Attribute, MutableBoolean>> getVertexAttributes() {
        return vertex_attributes;
    }

    @Override
    public Map<Edge, Map<Attribute, MutableBoolean>> getSelectedEdgeAttributes() {
        HashMap<Edge, Map<Attribute, MutableBoolean>> map = new HashMap<Edge, Map<Attribute, MutableBoolean>>();
        Object[] cells;
        if (jgraph.isSelectionEmpty()) {
            cells = jgraph.getChildEdges(jgraph.getDefaultParent());
        } else {
            cells = jgraph.getSelectionCells();
        }

        for (Object cell : cells) {
            Edge e = cell_edge.get(cell);
            if (e != null)
                map.put(e, edge_attributes.get(e));
        }
        return map;
    }

    @Override
    public Map<Vertex, Map<Attribute, MutableBoolean>> getSelectedVertexAttributes() {
        HashMap<Vertex, Map<Attribute, MutableBoolean>> map = new HashMap<Vertex, Map<Attribute, MutableBoolean>>();
        Object[] cells;
        if (jgraph.isSelectionEmpty()) {
            cells = jgraph.getChildVertices(jgraph.getDefaultParent());
        } else {
            cells = jgraph.getSelectionCells();
        }

        for (Object cell : cells) {
            Vertex v = cell_vertex.get(cell);
            if (v != null)
                map.put(v, vertex_attributes.get(v));
        }
        return map;
    }

    @Override
    public Graph getOriginGraph() {
        return graph;
    }

    @Override
    public Graph getCopyGraph() {
        return graph.clone();
    }

    @Override
    public int getDataBaseGraphId() {
        return graph.getStorableId();
    }

    @Override
    public int getDataBaseGraphModelId() {
        return graph_model_id;
    }

    @Override
    public boolean isDirected() {
        return graph.isDirected();
    }

    @Override
    public Graph getOriginalSelectedSubGraph() {
        // get vertices
        Object[] cells;
        if (jgraph.isSelectionEmpty()) {
            cells = jgraph.getChildVertices(jgraph.getDefaultParent());
        } else {
            cells = jgraph.getSelectionCells();
        }

        List<Vertex> new_vertices = new ArrayList<Vertex>();
        for (Object cell : cells) {
            Vertex v = cell_vertex.get(cell);
            if (v != null)
                new_vertices.add(v);
        }

        // get edges
        if (jgraph.isSelectionEmpty()) {
            cells = jgraph.getChildEdges(jgraph.getDefaultParent());
        } else {
            cells = jgraph.getSelectionCells();
        }

        Collection<Edge> new_edges = new ArrayList<Edge>();
        for (Object cell : cells) {
            Edge e = cell_edge.get(cell);
            new_edges.add(e);
        }

        Graph new_g = new Graph(new_vertices, new_edges, graph.isDirected());
        new_g.setStorableId(graph.getStorableId());
        return new_g;
    }

    @Override
    public Graph getCopySelectedSubGraph() {
        // clone vertices
        Object[] cells;
        if (jgraph.isSelectionEmpty()) {
            cells = jgraph.getChildVertices(jgraph.getDefaultParent());
        } else {
            cells = jgraph.getSelectionCells();
        }

        Map<Vertex, Vertex> new_vertices = new HashMap<Vertex, Vertex>();
        for (Object cell : cells) {
            Vertex v = cell_vertex.get(cell);
            if (v != null)
                new_vertices.put(v, v.clone());
        }

        // clone edges
        if (jgraph.isSelectionEmpty()) {
            cells = jgraph.getChildEdges(jgraph.getDefaultParent());
        } else {
            cells = jgraph.getSelectionCells();
        }

        Collection<Edge> new_edges = new ArrayList<Edge>();
        for (Object cell : cells) {
            Edge e = cell_edge.get(cell);
            if (e != null) {
                Vertex src = e.getSource();
                Vertex trg = e.getTarget();
                if (src != null && trg != null) {
                    Vertex src_new = new_vertices.get(src);
                    Vertex trg_new = new_vertices.get(trg);
                    //if (src_new != null && trg_new != null)
                    new_edges.add(new Edge(src_new, trg_new, ((AttributeItem)e).clone()));
                }
            }
        }

        Graph new_g = new Graph(new_vertices.values(), new_edges, graph.isDirected());
        //new_g.setGraphViewId(graph.getGraphViewId());
        new_g.setStorableId(graph.getStorableId());
        return new_g;
    }

    @Override
    public boolean isSelect(Vertex vertex) {
        // TODO
        throw new NotImplementedException("isSelect not implemented");
    }

    @Override
    public boolean isSelect(Edge edge) {
        // TODO
        throw new NotImplementedException("isSelect not implemented");
    }

    @Override
    public boolean isPanning() {
        return panning;
    };

//==============================================================================
//-----------------Graph extensions methods-------------------------------------
    @Override
    public void addPopupMenuItem(JMenuItem item) {
        popup.add(item);
    }

    @Override
    public void removePopupMenuItem(JMenuItem item) {
        popup.remove(item);
    }

//==============================================================================
//-----------------Methods for editing graph------------------------------------
    @Override
    public void setInvisibleAllEdgesBut(Set<Edge> edges) {
        if (edges == null)
            return;

        Object parent = jgraph.getDefaultParent();
        Object[] cells = jgraph.getChildEdges(parent);

        jgraph.getModel().beginUpdate();
        for (Object cell : cells) {
            Edge e = cell_edge.get(cell);
            if (e != null) {
                if (edges.contains(e)) {
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 100);
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 100);
                } else {
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 20);
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 20);
                }
            }
        }
        jgraph.getModel().endUpdate();
    }

    @Override
    public void setInvisibleAllVerticesBut(Set<Vertex> vertices) {
        if (vertices == null)
            return;

        Object parent = jgraph.getDefaultParent();
        Object[] cells = jgraph.getChildVertices(parent);

        jgraph.getModel().beginUpdate();
        for (Object cell : cells) {
            Vertex v = cell_vertex.get(cell);
            if (v != null) {
                if (vertices.contains(v)) {
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 100);
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 100);
                } else {
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 20);
                    ((mxCell)cell).setStyle(((mxCell)cell).getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 20);
                }
            }
        }
        jgraph.getModel().endUpdate();
    }

    @Override
    public void setInvisibleEdges(Collection<Edge> edges) {
        if (edges == null)
            return;

        jgraph.getModel().beginUpdate();
        for (Edge edge : edges) {
            mxCell cell = edge_cell.get(edge);
            if (cell != null) {
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 20);
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 20);
            }
        }
        jgraph.getModel().endUpdate();
    }

    @Override
    public void setVisibleEdges(Collection<Edge> edges) {
        if (edges == null)
            return;

        jgraph.getModel().beginUpdate();
        for (Edge edge : edges) {
            mxCell cell = edge_cell.get(edge);
            if (cell != null) {
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 100);
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 100);
            }
        }
        jgraph.getModel().endUpdate();
    }

    @Override
    public void setInvisibleVertices(Collection<Vertex> vertices) {
        if (vertices == null)
            return;

        jgraph.getModel().beginUpdate();
        for (Vertex vertex : vertices) {
            mxCell cell = vertex_cell.get(vertex);
            if (cell != null) {
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 20);
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 20);
            }
        }
        jgraph.getModel().endUpdate();
    }

    @Override
    public void setVisibleVertices(Collection<Vertex> vertices) {
        if (vertices == null)
            return;

        jgraph.getModel().beginUpdate();
        for (Vertex vertex : vertices) {
            mxCell cell = vertex_cell.get(vertex);
            if (cell != null) {
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_OPACITY + "=" + 100);
                cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_TEXT_OPACITY + "=" + 100);
            }
        }
        jgraph.getModel().endUpdate();
    }

    @Override
    public void selectElements(Set<Vertex> vertices, Set<Edge> edges) {
        ArrayList<Object> selection = new ArrayList<Object>();

        for (Vertex v : vertices) {
            mxCell cell = vertex_cell.get(v);
            if (cell != null)
                selection.add(cell);
        }

        for (Edge e : edges) {
            mxCell cell = edge_cell.get(e);
            if (cell != null)
                selection.add(cell);
        }

        jgraph.setSelectionCells(selection);

        jgraph.setSelectionCells(jgraph.getSelectionCells());
    }

    @Override
    public void showAttributes(Set<String> vertexAttr, Set<String> edgeAttr) {
        Object[] cells;
        if (jgraph.isSelectionEmpty()) {
            cells = jgraph.getChildCells(jgraph.getDefaultParent());
        } else {
            cells = jgraph.getSelectionCells();
        }

        jgraph.getModel().beginUpdate();

        for (Object cell : cells) {
            // vertices
            Vertex v = cell_vertex.get(cell);
            if (v != null) {
                Map<Attribute, MutableBoolean> map = vertex_attributes.get(v);
                for (Attribute attr : map.keySet()) {
                    String name = attr.getName();
                    if (name != null && vertexAttr.contains(name)) {
                        map.get(attr).setValue(true);
                    } else {
                        map.get(attr).setValue(false);
                    }
                }
                String text = generate_text4vertex(v);
                ((mxCell) cell).setValue(text);
            }

            mxCellState cellState = jgraph.getView().getState(cell);
            if(cellState != null) {
                cellState.setStyle(jgraph.getCellStyle(cell));
            } else {
                MainManager.logger.printError("[" + this.getClass().getName() + ".showAttributes()] Cell state is null.");
            }

            // edges
            Edge e = cell_edge.get(cell);
            if (e != null) {
                Map<Attribute, MutableBoolean> map = edge_attributes.get(e);
                for (Attribute attr : map.keySet()) {
                    String name = attr.getName();
                    if (name != null && edgeAttr.contains(name)) {
                        map.get(attr).setValue(true);
                    } else {
                        map.get(attr).setValue(false);
                    }
                }
                String text = generate_text4edge(e);
                ((mxCell) cell).setValue(text);
            }
        }

        jgraph.getModel().endUpdate();

        double x = 0;
        double y = 0;
        cells = jgraph.getChildCells(jgraph.getDefaultParent());
        for (Object cell : cells) {
            mxCellState state = jgraph.getView().getState(cell);
            if (state != null) {
                jgraph.getView().updateLabelBounds(state);
                if (state.getLabelBounds().getX() < x)
                    x = state.getLabelBounds().getX();
                if (state.getLabelBounds().getY() < y)
                    y = state.getLabelBounds().getY();
            }
        }

        if (x < 0 || y < 0) {
            final mxPoint translate = new mxPoint(-x, -y);
            jgraph.moveCells(jgraph.getChildCells(jgraph.getDefaultParent()), translate.getX(), translate.getY());
            jgraph.repaint();
        }

        // apply settings
        apply_settings();

        // m.b. need call repaint
        jgraph_view.refresh();
    }

    @Override
    public int setColor4Vertex(String c, Vertex v) {
        jgraph.getModel().beginUpdate();
        int re = set_color4vertex(c, v);
        jgraph.getModel().endUpdate();

        // m.b. need call repaint
        jgraph_view.refresh();
        return re;
    }

    @Override
    public int setColor4Edge(String c, Edge e) {
        jgraph.getModel().beginUpdate();
        int re = set_color4edge(c, e);
        jgraph.getModel().endUpdate();

        // m.b. need call repaint
        jgraph_view.refresh();
        return re;
    }

    @Override
    public int setShape4Vertex(String shape, Vertex v) {
        jgraph.getModel().beginUpdate();
        int re = set_shape4vertex(shape, v);
        jgraph.getModel().endUpdate();

        // m.b. need call repaint
        jgraph_view.refresh();
        return re;
    }

    @Override
    public int setShape4Edge(String shape, Edge e) {
        jgraph.getModel().beginUpdate();
        int re = set_shape4edge(shape, e);
        jgraph.getModel().endUpdate();

        // m.b. need call repaint
        jgraph_view.refresh();
        return re;
    }

    @Override
    public void setStyle(String style, Object value) {
        set_element_style_without_applying(style, value);
        apply_settings();
    }

    @Override
    public void setStylesheet(Map<String, Object> stylesheet) {
        if (stylesheet == null)
            return;

        for (String key : stylesheet.keySet()) {
            set_element_style_without_applying(key, stylesheet.get(key));
        }

        apply_settings();
    }

    @Override
    public void setPanning(boolean panning) {
        this.panning = panning;
    }

    @Override
    public void zoomIn() {
        jgraph_view.zoomIn();
    }

    @Override
    public void zoomOut() {
        jgraph_view.zoomOut();
    }

    @Override
    public void showCriticalPath(Vertex source, Vertex target, final boolean maximum, final String attrName) {
        // initialize from and to
        mxCell from = vertex_cell.get(source);
        mxCell to = vertex_cell.get(target);
        if (from == null || to == null) return;

        Object parent = jgraph.getDefaultParent();
        mxGraphAnalysis mga = mxGraphAnalysis.getInstance();
        Object[] path = mga.getShortestPath(jgraph, from, to, new mxICostFunction() {
            @Override
            public double getCost(mxCellState state) {
                double length = 0;
                boolean attrExist = false;

                if (cell_edge.get(state.getCell()) == null)
                    return Double.POSITIVE_INFINITY;

                for (Attribute attr : cell_edge.get(state.getCell()).getAttributes()) {
                    if (attrName.equals(attr.getName()) && attr.isRealType()) {
                        length = attr.getRealValue();
                        attrExist = true;
                    }
                }
                if (!attrExist)
                    return Double.POSITIVE_INFINITY;
                if (maximum)
                    return 1 / length;
                else
                    return length;
            }
        }, jgraph.getChildEdges(parent).length, graph.isDirected());

        if (path.length > 0) {
            jgraph.setSelectionCells(path);
        } else {
            MainManager.windowMessage.infoMessage("Critical path was not found between vertexes " + source.getStorableId() + " to " + target.getStorableId(), "Critical path messsage");
        }
        jgraph.setSelectionCells(path);
    }

    @Override
    public void showCycles(final Vertex v) {
        ArrayList<Object> cycle = findPath(v, v);
        if (cycle != null && cycle.size() > 1) {
            jgraph.setSelectionCells(cycle);
        } else {
            MainManager.windowMessage.infoMessage("Cicle was not found with vertex " + v.getStorableId(), "Cicle messsage");
        }
    }

    @Override
    public void showPaths(final Vertex source, final Vertex target) {
        ArrayList<Object> paths = findPath(source, target);
        if (paths != null && paths.size() > 0) {
            jgraph.setSelectionCells(paths);
        } else {
            MainManager.windowMessage.infoMessage("Path was not found beetween vertexes " + source.getStorableId() + " to " + target.getStorableId(), "Path messsage");
        }
    }

    @Override
    public mxGraph getMxGraph() {
        return jgraph;
    }

    @Override
    public mxGraphComponent getMxGraphComponent() {
        return jgraph_view;
    }

//==============================================================================
//-----------------Undo/Redo methods--------------------------------------------
    @Override
    public void undo() {
        jgraph_undo_manager.undo();
    }

    @Override
    public void redo() {
        jgraph_undo_manager.redo();
    }

//==============================================================================
//-----------------PRIVATE METHODS----------------------------------------------
    /**
     * Initializing of mxGraph and mxGraphComponent.
     * Enabling/Disabling of options for graph.
     * Adding event handlers
     */
    private void gui_setup() {
        jgraph = new mxGraph();

        jgraph.setAllowNegativeCoordinates(false);
        jgraph.setAlternateEdgeStyle("edgeStyle=mxEdgeStyle.ElbowConnector;elbow=vertical");

        jgraph_undo_manager = new mxUndoManager();
        jgraph_view = new mxGraphComponent(jgraph) {
            private static final long serialVersionUID = 1L;
            public boolean isPanningEvent(MouseEvent event) {
                return (event != null) ? (event.getButton() == MouseEvent.BUTTON2 || panning ||
                        (event.isShiftDown() && event.isControlDown())) : false;
            }
        };

        observers = new ArrayList<IGraphObserver>();

        jgraph.setCellsCloneable(false);
        //jgraph.setGridEnabled(false);
        jgraph.setCellsDeletable(false);
        jgraph.setHtmlLabels(true);
        jgraph.setMultigraph(true);
        jgraph.setCellsDisconnectable(false);
        jgraph.setAllowDanglingEdges(false);
        jgraph.setCellsEditable(false);
        //jgraph.setGridSize(4);
        //jgraph_view.setConnectable(false);
        jgraph_view.setToolTips(true);
        //jgraph_view.setDragEnabled(false);
        //jgraph_view.setZoomFactor(2);
        // allow rubberband selection
        new mxRubberband(jgraph_view);
        // allow use keyboard
        new mxKeyboardHandler(jgraph_view);

        // minimap
        jgraph_minimap = new mxGraphOutline(jgraph_view);
        MouseWheelListener wheelTracker = new MouseWheelListener() {

            public void mouseWheelMoved(MouseWheelEvent e) {
                if (e.getSource() instanceof mxGraphOutline || e.isControlDown()) {
                    if (e.getWheelRotation() < 0) {
                        jgraph_view.zoomIn();
                    } else {
                        jgraph_view.zoomOut();
                    }
                }

            }

        };
        jgraph_minimap.addMouseWheelListener(wheelTracker);
        jgraph_view.addMouseWheelListener(wheelTracker);

        // undo/redo event handlers
        mxEventSource.mxIEventListener undoHandler = new mxEventSource.mxIEventListener() {
            public void invoke(Object sender, mxEventObject evt) {
                List<mxUndoableEdit.mxUndoableChange> changes = ((mxUndoableEdit) evt.getProperty("edit")).getChanges();
                jgraph.setSelectionCells(jgraph.getSelectionCellsForChanges(changes));

            }
        };

        mxEventSource.mxIEventListener undoHandle = new mxEventSource.mxIEventListener() {
            public void invoke(Object sender, mxEventObject evt) {
                jgraph_undo_manager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit"));
            }
        };

        jgraph.getModel().addListener(mxEvent.UNDO, undoHandle);
        jgraph.getView().addListener(mxEvent.UNDO, undoHandle);

        jgraph_undo_manager.addListener(mxEvent.UNDO, undoHandler);
        jgraph_undo_manager.addListener(mxEvent.REDO, undoHandler);

        // selection handler
        mxEventSource.mxIEventListener selectHandler = new mxEventSource.mxIEventListener() {
            public void invoke(Object sender, mxEventObject evt) {
                turnObservers_updateSelectedElements();
            }
        };
        jgraph.getSelectionModel().addListener(mxEvent.CHANGE, selectHandler);

        // move handler
        mxEventSource.mxIEventListener moveHandler = new mxEventSource.mxIEventListener() {
            public void invoke(Object sender, mxEventObject evt) {
                double x = 0, y = 0;
                for (Object o : (Object[]) evt.getProperty("cells")) {
                    mxCell cell = (mxCell) o;
                    if (cell.getGeometry().getX() < x)
                        x = cell.getGeometry().getX();
                    if (cell.getGeometry().getY() < y)
                        y = cell.getGeometry().getY();
                }
                if (x != 0 || y != 0)
                    jgraph.moveCells(jgraph.getChildCells(jgraph.getDefaultParent()), -x, -y);
            }
        };

        jgraph.addListener(mxEvent.CELLS_MOVED, moveHandler);

        // popup menu
        popup = new JPopupMenu();
        final JMenuItem openSubGraphMenuItem = new JMenuItem("Open subgraph in new tab");
        openSubGraphMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                AGraphView subgraph = MainManager.graphViewManager.newGraphView(parent, getCopySelectedSubGraph());
                UIRequestAddTab r = new UIRequestAddTab(subgraph, null);
                MainManager.pluginParameter.userInterfaceManager.sendRequest(r);
            }
        });

        final JMenuItem addVertexMenuItem = new JMenuItem("Add vertex");
        addVertexMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                int vertexId = MainManager.graphDataBase.createVertexHeader(graph.getStorableId(), -1);
                Vertex v = MainManager.graphDataBase.getVertex(vertexId);
                addVertex(v);
                AddVertexEvent event = new AddVertexEvent(v);
                MainManager.view.sendEvent(event);
            }
        });

        final JMenuItem addEdgeMenuItem = new JMenuItem("Add edge");
        addEdgeMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                Object[] cells;
                cells = jgraph.getSelectionCells();

                if (cells != null && cells.length == 2) {
                    Vertex src = cell_vertex.get(cells[0]);
                    Vertex trg = cell_vertex.get(cells[1]);
                    if (src != null && trg != null) {
                        int edgeId = MainManager.graphDataBase.createEdgeHeader(graph.getStorableId(), src.getStorableId(), trg.getStorableId());
                        Edge e = MainManager.graphDataBase.getEdge(edgeId);
                        addEdge(e);
                        AddEdgeEvent event = new AddEdgeEvent(e);
                        MainManager.view.sendEvent(event);
                    }
                }
                if (cells != null && cells.length == 1) {
                    Vertex src = cell_vertex.get(cells[0]);
                    Vertex trg = cell_vertex.get(cells[0]);
                    if (src != null && trg != null) {
                        int edgeId = MainManager.graphDataBase.createEdgeHeader(graph.getStorableId(), src.getStorableId(), trg.getStorableId());
                        Edge e = MainManager.graphDataBase.getEdge(edgeId);
                        addEdge(e);
                        AddEdgeEvent event = new AddEdgeEvent(e);
                        MainManager.view.sendEvent(event);
                    }
                }
            }
        });

        final JMenuItem addAttributeMenuItem = new JMenuItem("Add attribute");
        addAttributeMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                Object[] cells;
                cells = jgraph.getSelectionCells();

                if (cells != null && cells.length == 1) {
                    Attribute attr = null;
                    Vertex vertex = cell_vertex.get(cells[0]);
                    if (vertex != null) {
                        attr = SharedGraphEditor.addAttribute4Vertex(vertex);
                        addAttribute4Vertex(attr, vertex);
                    }

                    Edge edge =  cell_edge.get(cells[0]);
                    if (edge != null) {
                        attr = SharedGraphEditor.addAttribute4Edge(edge);
                        addAttribute4Edge(attr, edge);
                    }

                    if (attr != null) {
                        AddAttributeEvent event = new AddAttributeEvent(attr);
                        MainManager.view.sendEvent(event);
                    }
                }
            }
        });

        popup.add(openSubGraphMenuItem);
        popup.add(addVertexMenuItem);
        popup.add(addEdgeMenuItem);
        popup.add(addAttributeMenuItem);

        jgraph_view.getGraphControl().addMouseListener(new MouseAdapter() {
            private void popupTriger(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    openSubGraphMenuItem.setEnabled(true);

                    mxCell cell = (mxCell) jgraph_view.getCellAt(e.getX(), e.getY());
                    popup.show(e.getComponent(), e.getX(), e.getY());
                }
                SwingUtilities.updateComponentTreeUI(popup);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.getClickCount() > 1) {
                    mxCell cell = (mxCell) jgraph_view.getCellAt(e.getX(), e.getY());
                    if (cell != null) {
                        Vertex v = cell_vertex.get(cell);
                        if (v != null) {
                            Graph g = v.getInnerGraph();
                            if (g != null) {
                                int storableId = g.getStorableId();
                                if (storableId >= 0)
                                    g = MainManager.graphDataBase.getGraph(storableId);
                                if (g != null && !g.isEmpty()) {
                                    AGraphView view = new SmartGraphView(g);
                                    UIRequestAddTab r = new UIRequestAddTab(view, null);
                                    MainManager.pluginParameter.userInterfaceManager.sendRequest(r);
                                }
                            }
                        }
                    }
                }
                popupTriger(e);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                popupTriger(e);
            }
        });

        // apply graph settings
        initDefaultGraphSettings();
        apply_settings();

    }

    private void initDefaultGraphSettings() {
        // set default values
        spacingBottom = 0;
        spacingTop = 0;
        spacingLeft = 0;
        spacingRight = 0;
        fontColor = "black";
        fillColor = "white";
        vertexShape = mxConstants.SHAPE_RECTANGLE;
        graphElementsFont = "Monospaced";
        vertexTextAlign = mxConstants.ALIGN_LEFT;

        // initialize style sheet constant
        styleSheet = new mxStylesheet();
    }

    private void store_attributes4vertex(Vertex vertex) {
        if (vertex == null) return;

        List<Attribute> attributes = vertex.getAttributes();
        if (attributes == null) attributes = Collections.emptyList();

        Map<Attribute, MutableBoolean> map = new HashMap<Attribute, MutableBoolean>(attributes.size());
        for (Attribute buf : attributes) {
            String name = buf.getName();
            if (name == null) continue;

            if (name.equals(DEF_NAME_ATTRIBUTE)) {
                map.put(buf, new MutableBoolean(true));
            } else {
                map.put(buf, new MutableBoolean(false));
            }
        }
        vertex_attributes.put(vertex, map);
    }

    // Store attribute and make it visible
    private void store_attribute4vertex(Vertex vertex, Attribute attr) {
        // check input parameters
        if (vertex == null || attr == null)
            return;

        if (attr.getName() == null || attr.isNullType())
            return;

        // store attribute for vertex
        Map<Attribute, MutableBoolean> map = vertex_attributes.get(vertex);
        if (map == null)
            map = new HashMap<Attribute, MutableBoolean>();

        map.remove(attr);
        map.put(attr, new MutableBoolean(true));

        vertex_attributes.put(vertex, map);
    }

    private void store_attributes4edge(Edge edge) {
        if (edge == null) return;

        List<Attribute> attributes = edge.getAttributes();
        if (attributes == null) attributes = Collections.emptyList();

        Map<Attribute, MutableBoolean> map = new HashMap<Attribute, MutableBoolean>(attributes.size());
        for (Attribute buf : attributes) {
            if (buf.getName() == null) continue;

            map.put(buf, new MutableBoolean(false));
        }
        edge_attributes.put(edge, map);
    }

    // Store attribute and make it visible
    private void store_attribute4edge(Edge edge, Attribute attr) {
        // check input parameters
        if (edge == null || attr == null)
            return;

        if (attr.getName() == null || attr.isNullType())
            return;

        // store attribute for edge
        Map<Attribute, MutableBoolean> map = edge_attributes.get(edge);
        if (map == null)
            map = new HashMap<Attribute, MutableBoolean>();

        map.remove(attr);
        map.put(attr, new MutableBoolean(true));

        edge_attributes.put(edge, map);
    }

    private void store_attributes4vertex(Vertex vertex, Map<Attribute, MutableBoolean> to) {
        if (vertex == null) return;

        List<Attribute> attributes = vertex.getAttributes();
        if (attributes == null) attributes = Collections.emptyList();

        Map<Attribute, MutableBoolean> map = new HashMap<Attribute, MutableBoolean>(attributes.size());
        for (Attribute buf : attributes) {
            String name = buf.getName();
            if (name == null) continue;

            boolean check = false;
            for (Attribute to_buf : to.keySet()) {
                String to_name = to_buf.getName();
                if (to_name != null && to_name.equals(name)) {
                    map.put(buf, new MutableBoolean(to.get(to_buf).booleanValue()));
                    check = true;
                    break;
                }
            }

            if (check == false) {
                map.put(buf, new MutableBoolean(false));
            }
        }
        vertex_attributes.put(vertex, map);
    }

    private void store_attributes4edge(Edge edge, Map<Attribute, MutableBoolean> to) {
        if (edge == null) return;

        List<Attribute> attributes = edge.getAttributes();
        if (attributes == null) attributes = Collections.emptyList();

        Map<Attribute, MutableBoolean> map = new HashMap<Attribute, MutableBoolean>(attributes.size());
        for (Attribute buf : attributes) {
            String name = buf.getName();
            if (name == null) continue;

            boolean check = false;
            for (Attribute to_buf : to.keySet()) {
                String to_name = to_buf.getName();
                if (to_name != null && to_name.equals(name)) {
                    map.put(buf, new MutableBoolean(to.get(to_buf).booleanValue()));
                    check = true;
                    break;
                }
            }

            if (check == false) {
                map.put(buf, new MutableBoolean(false));
            }
        }
        edge_attributes.put(edge, map);
    }

    private String generate_text4vertex(Vertex vertex) {
        Map<Attribute, MutableBoolean> map = vertex_attributes.get(vertex);

        if (map == null) return "";

        StringBuilder title = new StringBuilder(100);

        for (Attribute attr : map.keySet()) {
            String name = attr.getName();
            String value = attr.toNotNullStringValue();
            if (name == null)
                continue;

            if (map.get(attr).isFalse())
                continue;

            if (name.equals(DEF_NAME_ATTRIBUTE)) {
                StringBuilder newTitle = new StringBuilder(title.length() + 100);
                newTitle.append("<b>");
                newTitle.append(value.replaceAll("\\n", "<br>"));
                newTitle.append("</b><br>");
                newTitle.append(title);
                title = newTitle;
            } else {
                title.append("<b>");
                title.append(name);
                title.append("</b>");
                title.append(":");
                title.append(value.replaceAll("\\n", "<br>"));
                title.append("<br>");
            }
        }

        if (title.length() > 0) {
            title.insert(0, "<html>");
            title.append("</html>");
        }

        return title.toString();
    }

    private void auto_generate_style4vertex(Vertex vertex) {
        Map<Attribute, MutableBoolean> map = vertex_attributes.get(vertex);

        if (map == null) return;

        for (Attribute attr : map.keySet()) {
            String name = attr.getName();
            String value = attr.getStringValue();
            if (name == null)
                continue;
            if (value == null)
                continue;

            if (name.equals(DEF_SHAPE_ATTRIBUTE)) {
                set_shape4vertex(value, vertex);
            } else if (name.equals(DEF_COLOR_ATTRIBUTE)) {
                set_color4vertex(value, vertex);
            }
        }
    }

    private void auto_generate_style4edge(Edge edge) {
        Map<Attribute, MutableBoolean> map = vertex_attributes.get(edge);

        if (map == null) return;

        for (Attribute attr : map.keySet()) {
            String name = attr.getName();
            String value = attr.getStringValue();
            if (name == null)
                continue;
            if (value == null)
                continue;

            if (name.equals(DEF_SHAPE_ATTRIBUTE)) {
                set_shape4edge(value, edge);
            } else if (name.equals(DEF_COLOR_ATTRIBUTE)) {
                set_color4edge(value, edge);
            }
        }
    }

    private String generate_text4edge(Edge edge) {
        Map<Attribute, MutableBoolean> map = edge_attributes.get(edge);

        if (map == null) return "";

        StringBuilder title = new StringBuilder(100);
        for (Attribute attr : map.keySet()) {
            String name = attr.getName();
            String value = attr.toNotNullStringValue();

            if (map.get(attr).isFalse())
                continue;

            title.append("<b>");
            title.append(name);
            title.append("</b>");
            title.append(":");
            title.append(value.replaceAll("\\n", "<br>"));
            title.append("<br>");
        }

        if (title.length() > 0) {
            title.insert(0, "<html>");
            title.append("</html>");
        }

        return title.toString();
    }

    private void mark_vertices_with_inner_graphs() {
        jgraph.getModel().beginUpdate();
        for (Vertex vertex : vertex_cell.keySet()) {
            Graph innerGraph = vertex.getInnerGraph();
            if (innerGraph != null && innerGraph.getStorableId() >= 0) {
                mxCell cellVertex = vertex_cell.get(vertex);
                cellVertex.setStyle(cellVertex.getStyle() + ";" + mxConstants.STYLE_FONTSTYLE + "=" + mxConstants.FONT_UNDERLINE);
            }
        }
        jgraph.getModel().endUpdate();
    }

    // Use only between beginUpdate() mark_vertex_with_inner_graph(v) endUpdate()
    private void mark_vertex_with_inner_graph(Vertex vertex) {
        Graph innerGraph = vertex.getInnerGraph();
        if (innerGraph != null && innerGraph.getStorableId() >= 0) {
            mxCell cellVertex = vertex_cell.get(vertex);
            cellVertex.setStyle(cellVertex.getStyle() + ";" + mxConstants.STYLE_FONTSTYLE + "=" + mxConstants.FONT_UNDERLINE);
        }
    }

    private void turnObservers_updateSelectedElements() {
        final Object[] arrLocal = observers.toArray();

        // init
        final Map<Vertex, Map<Attribute, MutableBoolean>> selectedVertexAttributes = getSelectedVertexAttributes();
        final Map<Edge, Map<Attribute, MutableBoolean>> selectedEdgeAttributes = getSelectedEdgeAttributes();

        final Map<Attribute, MutableBoolean> selectedVertexAttributesOnly = new HashMap<Attribute, MutableBoolean>();
        final Map<Attribute, MutableBoolean> selectedEdgeAttributesOnly = new HashMap<Attribute, MutableBoolean>();

        // collect
        if (selectedVertexAttributes != null) {
            for (Map<Attribute, MutableBoolean> buf : selectedVertexAttributes.values()) {
                if (buf != null)
                    selectedVertexAttributesOnly.putAll(buf);
            }
        }

        if (selectedEdgeAttributes != null) {
            for (Map<Attribute, MutableBoolean> buf : selectedEdgeAttributes.values()) {
                if (buf != null)
                    selectedEdgeAttributesOnly.putAll(buf);
            }
        }

        // call update methods
        for (int i = 0; i < arrLocal.length; i++) {
            try {
                ((IGraphObserver) arrLocal[i]).updateSelectedElements(selectedVertexAttributes, selectedEdgeAttributes);
            } catch (Throwable ex) {
                MainManager.logger.printException(ex);
            }

            try {
                ((IGraphObserver) arrLocal[i]).updateSelectedElementsAttributes(selectedVertexAttributesOnly, selectedEdgeAttributesOnly);
            } catch (Exception ex) {
                MainManager.logger.printException(ex);
            }
        }
    }

    private void turnObservers_resetGraph() {
        final Object[] arrLocal = observers.toArray();

        // call update methods
        for (int i = 0; i < arrLocal.length; i++) {
            try {
                ((IGraphObserver) arrLocal[i]).resetGraph();
            } catch (Throwable ex) {
                MainManager.logger.printException(ex);
            }
        }
    }

    private void turnObservers_changeGraphStructure() {
        final Object[] arrLocal = observers.toArray();

        // call update methods
        for (int i = 0; i < arrLocal.length; i++) {
            try {
                ((IGraphObserver) arrLocal[i]).changeGraphStructure();
            } catch (Throwable ex) {
                MainManager.logger.printException(ex);
            }
        }
    }

    public int set_color4vertex(String c, Vertex v) {
        mxCell cell = vertex_cell.get(v);
        if (cell != null && check_color(c)) {
            cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_FILLCOLOR + "=" + c);
            return 0;
        }

        return -1;
    }

    public int set_color4edge(String c, Edge e) {
        mxCell cell = edge_cell.get(e);
        if (cell != null && check_color(c)) {
            cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_FILLCOLOR + "=" + c);
            return 0;
        }

        return -1;
    }

    public int set_shape4vertex(String shape, Vertex v) {
        mxCell cell = vertex_cell.get(v);
        if (cell != null && check_shape(shape)) {
            cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_SHAPE + "=" + shape);
            return 0;
        }

        return -1;
    }

    public int set_shape4edge(String shape, Edge e) {
        mxCell cell = edge_cell.get(e);
        if (cell != null && check_shape(shape)) {
            cell.setStyle(cell.getStyle() + ";" + mxConstants.STYLE_SHAPE + "=" + shape);
            return 0;
        }

        return -1;
    }

    private boolean check_color(String color) {
        try {
            mxUtils.parseColor(color);
            return true;
        } catch (Throwable ex) {
            return false;
        }
    }

    private boolean check_shape(String shape) {
        if (mxConstants.SHAPE_RECTANGLE.equals(shape) ||
                mxConstants.SHAPE_ELLIPSE.equals(shape) ||
                mxConstants.SHAPE_DOUBLE_ELLIPSE.equals(shape) ||
                mxConstants.SHAPE_RHOMBUS.equals(shape) ||
                mxConstants.SHAPE_LINE.equals(shape) ||
                mxConstants.SHAPE_IMAGE.equals(shape) ||
                mxConstants.SHAPE_ARROW.equals(shape) ||
                mxConstants.SHAPE_CURVE.equals(shape) ||
                mxConstants.SHAPE_LABEL.equals(shape) ||
                mxConstants.SHAPE_CYLINDER.equals(shape) ||
                mxConstants.SHAPE_SWIMLANE.equals(shape) ||
                mxConstants.SHAPE_CONNECTOR.equals(shape) ||
                mxConstants.SHAPE_ACTOR.equals(shape) ||
                mxConstants.SHAPE_CLOUD.equals(shape) ||
                mxConstants.SHAPE_TRIANGLE.equals(shape) ||
                mxConstants.SHAPE_HEXAGON.equals(shape)) {
            return true;
        }
        return false;
    }

    //=============================================================================
//-----------------Methods for working with graph settings---------------------
    public void set_element_style_without_applying(String style, Object value) {
        if (style == null || value == null)
            return;

        try {
            if (style.equals(mxConstants.STYLE_SPACING_LEFT))
                spacingLeft = (Integer)value;
            if (style.equals(mxConstants.STYLE_SPACING_RIGHT))
                spacingRight = (Integer)value;
            if (style.equals(mxConstants.STYLE_SPACING_TOP))
                spacingTop = (Integer)value;
            if (style.equals(mxConstants.STYLE_SPACING_BOTTOM))
                spacingBottom = (Integer)value;
            if (style.equals(mxConstants.STYLE_FONTFAMILY))
                graphElementsFont = (String)value;
            if (style.equals(mxConstants.STYLE_FILLCOLOR))
                fillColor = (String)value;
            if (style.equals(mxConstants.STYLE_FONTCOLOR))
                fontColor = (String)value;
            if (style.equals(mxConstants.STYLE_SHAPE))
                vertexShape = (String)value;
            if (style.equals(mxConstants.STYLE_ALIGN))
                vertexTextAlign = (String)value;
        } catch (Throwable ex) {
            MainManager.logger.printException(ex);
        }
    }

    private void apply_settings() {
        Map<String, Object> style = styleSheet.getDefaultVertexStyle();

        style.put(mxConstants.STYLE_SHAPE, vertexShape);
        style.put(mxConstants.STYLE_FONTCOLOR, fontColor);
        style.put(mxConstants.STYLE_FILLCOLOR, fillColor);
        style.put(mxConstants.STYLE_SPACING_LEFT, spacingLeft);
        style.put(mxConstants.STYLE_SPACING_RIGHT, spacingRight);
        style.put(mxConstants.STYLE_SPACING_TOP, spacingTop);
        style.put(mxConstants.STYLE_SPACING_BOTTOM, spacingBottom);
        style.put(mxConstants.STYLE_ALIGN, vertexTextAlign);
        style.put(mxConstants.STYLE_FONTFAMILY, graphElementsFont);

        style = styleSheet.getDefaultEdgeStyle();

        style.put(mxConstants.STYLE_FONTFAMILY, graphElementsFont);
        if (graph.isDirected())
            style.put(mxConstants.STYLE_ENDARROW, mxConstants.ARROW_CLASSIC);
        else
            style.put(mxConstants.STYLE_ENDARROW, mxConstants.NONE);
        style.put(mxConstants.STYLE_ROUNDED, "1");

        // apply settings
        jgraph.setStylesheet(styleSheet);
        Object parent = jgraph.getDefaultParent();
        jgraph.getModel().beginUpdate();
        for (Object cell : jgraph.getChildCells(parent)) {
            mxCellState state = jgraph.getView().getState(cell);
            state.setStyle(jgraph.getCellStyle(cell));
            jgraph.updateCellSize(cell);
        }
        jgraph.getModel().endUpdate();
        jgraph.refresh();
    }

    private ArrayList<Object> findPath(final Vertex source, final Vertex target) {
        mxCell from = vertex_cell.get(source);
        mxCell to = vertex_cell.get(target);
        if (from == null || to == null)
            return null;

        ArrayList<Object> paths = new ArrayList<Object>();
        ArrayList<Object> visited = new ArrayList<Object>();
        dfs(from, to, paths, visited, null);
        paths.add(from);
        return paths;
    }

    private void dfs(final Object root, final Object target, List<Object> paths, List<Object> visited, Object prevEdge) {
        visited.add(root);
        Object[] e = (graph.isDirected()) ? jgraph.getOutgoingEdges(root) : jgraph.getConnections(root);
        if (e == null)
            return;

        for (Object edge : e) {
            if (edge.equals(prevEdge))
                continue;
            Object[] opp = jgraph.getOpposites(new Object[] { edge }, root);

            if (opp != null && opp.length > 0) {
                Object o = opp[0];

                if (o.equals(target)) {
                    paths.add(o);
                    paths.add(edge);
                } else {
                    if (visited.contains(o))
                        continue;
                    int l = paths.size();
                    dfs(o, target, paths, visited, edge);
                    if (l != paths.size()) {
                        paths.add(edge);
                        paths.add(o);
                    }
                }
            }
        }
        visited.remove(root);
    }
}
